#ifndef A8_MSSQL_H
#define A8_MSSQL_H

#include <stdio.h>
#include <string>
#include <vector>
#include <a8/xvalue.hpp>
#include <a8/strutils.hpp>
#include <comutil.h>

#import "MSADO15.DLL" rename_namespace("ADOCG") rename("EOF","EndOfFile")
using namespace ADOCG;

namespace a8
{
    namespace mssql
    {

        static const char* ADO_CONNSTR = "Provider=SQLOLEDB.1;Password=%s;Persist Security Info=True;" \
            "User ID=%s;Initial Catalog=%s;Data Source=%s,%d;";
        
        class Connection
        {
        public:
			_ConnectionPtr mDBConnection;
            Connection(): mHost(""), mPort(0), mUserName(""), mPwd(""), mDataBase(""), mConnected(false)
            {
                mDBConnection.CreateInstance(__uuidof(ADOCG::Connection));
                assert(mDBConnection != NULL);
            }

            ~Connection()
            {
                mDBConnection.Release();
            }
            
            bool Connected() 
            {	 
                return mConnected; 
            }
            
            bool Connect(const std::string& host, int port, 
                         const std::string& userName, const std::string& pwd, const std::string& dataBase)
            {
                mHost = host;
                mPort = port;
                mUserName = userName;
                mPwd = pwd;
                mDataBase = dataBase;
                char connStr[512];
                sprintf(connStr, ADO_CONNSTR, mPwd.c_str(), mUserName.c_str(), mDataBase.c_str(), host.c_str(), mPort); 
                mLastError = mDBConnection->Open(_bstr_t(connStr), L"", L"", adConnectUnspecified);
                mConnected = !FAILED(mLastError);
                if (mConnected){
                    mDBConnection->CursorLocation = adUseClient;
                }
                return mConnected;	
            }
            
			bool ReConnect()
			{
				return Connect(mHost, mPort, mUserName, mPwd, mDataBase);
			}

            long GetErrNo()
            {
                return mLastError;
            }
            
            std::string GetError()
            {
                std::string errMsg;
                if (FAILED(mLastError)){
                    _com_error e(mLastError);
	#if _UNICODE
                    std::wstring ws = e.ErrorMessage();
                    errMsg = ws2s(ws);
	#else
                    errMsg = e.Description();
	#endif				
                }		
                return errMsg;
            }
            
        private:			
            std::string mHost;
            int mPort;
            std::string mUserName;
            std::string mPwd;
            std::string mDataBase;
            bool mConnected;	
            HRESULT mLastError;			
        };
        
        class Query
        {
        public:
            Query(): mConn(NULL)
            {
                mDBCommand.CreateInstance(__uuidof(::Command));
                mDBRecordset.CreateInstance(__uuidof(::Recordset));
                assert(mDBCommand != NULL);
                assert(mDBRecordset != NULL); 		
            }
            
            ~Query()
            {
                mDBCommand.Release();
                mDBRecordset.Release();
            }
            
			void SetConnection(a8::mssql::Connection *conn)
			{
				mConn = conn;
			}

            long GetErrNo()
            {
                return mLastError;
            }
            
            std::string GetError()
            {
                std::string errMsg;
                if (FAILED(mLastError)){
                    _com_error e(mLastError);
	#if _UNICODE
                    std::wstring ws = e.ErrorMessage();
                    errMsg = ws2s(ws);
	#else
                    errMsg = e.Description();
	#endif				
                }		
                return errMsg;
            }
            
            int ExecQuery(const std::string &sql)
            {
				CloseDataSet();
                assert(sql.length());
				try
				{
					mDBCommand->CommandText = sql.c_str();
					mDBCommand->ActiveConnection = mConn->mDBConnection;

					mDBRecordset->PutRefSource(mDBCommand);
					mDBRecordset->CursorLocation = adUseClient;
					mLastError = mDBRecordset->Open((IDispatch *)mDBCommand, vtMissing, adOpenForwardOnly, adLockReadOnly, adOptionUnspecified);
					return FAILED(mLastError) ? -1 : mDBRecordset->GetRecordCount();
				}
				catch (_com_error e)
				{
					printf("ExecQuery error %s\n", std::string(e.Description()).c_str());
					printf("sql: %s\n", sql.c_str());
					return -1;
				}
            }
            
			bool ExecScript(const std::string &sql)
            {
				int nRecordsAffected = 0;
				return ExecScript(sql, nRecordsAffected);
			}

            bool ExecScript(const std::string &sql, int& nRecordsAffected)
            {				
                assert(sql.length());
				CloseDataSet();
				try
				{
					mDBCommand->CommandText = sql.c_str();
					mDBCommand->ActiveConnection  = mConn->mDBConnection;			
		
					mDBCommand->ActiveConnection->CursorLocation = adUseClient;
					_variant_t recordsAffected;
					mLastError = mDBCommand->Execute(&recordsAffected, NULL, adExecuteNoRecords);
					if (!FAILED(mLastError)){
						nRecordsAffected = recordsAffected;
					}
					return !FAILED(mLastError);
				}
				catch (_com_error e)
				{
					printf("ExecScript error %s\n", std::string(e.Description()).c_str());
					printf("sql: %s\n", sql.c_str());
					return false;
				}
            }

			int ExecStoredProcedure(const char* spname, std::vector<a8::XValue>& inparams, 
				std::vector<std::string>& outparams, std::vector<a8::XValue>& retoutparams, int& nRecordsAffected)
			{
				for (int i = mDBCommand->Parameters->Count; i > 0; i--)
				{
					mDBCommand->Parameters->Delete(i - 1);
				}
				for (unsigned int i = 0; i < inparams.size(); i++){
					if (inparams[i].IsNumber()){
						mDBCommand->Parameters->Append(
							mDBCommand->CreateParameter("", adDouble, adParamInput, sizeof(long), 
								_variant_t((double)inparams[i].GetDouble()))
						);
					}else if(inparams[i].IsString()){
						mDBCommand->Parameters->Append(
							mDBCommand->CreateParameter("", adChar, adParamInput, inparams[i].GetString().length(), 
								_variant_t(inparams[i].GetString().c_str()))
						);
					}else{
						assert(false);
					}
				}	
				for (unsigned int i = 0; i < outparams.size(); i++){
					mDBCommand->Parameters->Append(
						mDBCommand->CreateParameter("", adChar, adParamOutput, 100, 
						_variant_t(outparams[i].c_str()))
					);
				}
				mDBCommand->CommandText = _bstr_t(spname);
				mDBCommand->ActiveConnection  = mConn->mDBConnection;			
				mDBCommand->CommandType = adCmdStoredProc;

				mDBCommand->ActiveConnection->CursorLocation = adUseClient;
				_variant_t recordsAffected;
				mLastError = mDBCommand->Execute(&recordsAffected, NULL, adCmdStoredProc);
				if(!FAILED(mLastError)){
					nRecordsAffected = recordsAffected;
				}				

				for (short i = 1; i < mDBCommand->Parameters->Count; i++){
					if (mDBCommand->Parameters->GetItem(i)->Direction == adParamOutput){
						a8::XValue xv;
						xv.SetVairent(mDBCommand->Parameters->GetItem(i)->Value);
						retoutparams.push_back(xv);
					}
				}				
				return 0;
			}
            
            void CloseDataSet()
            {
                if (mDBRecordset->GetState() != adStateClosed)
                    mDBRecordset->Close();
            }
            
            void First()
            {
                mDBRecordset->MoveFirst();
            }
            
            void Next()
            {
                mDBRecordset->MoveNext();
            }
            
            void Prev()
            {
                mDBRecordset->MovePrevious();
            }
            
            void Last()
            {
                mDBRecordset->MoveLast();
            }
            
            bool Eof()
            {				
                return mDBRecordset->EndOfFile == VARIANT_TRUE;
            }		
            
            int RowsCount()
            {
                return mDBRecordset->GetRecordCount();
            }
            
            int FiledsCount()
            {
                return mDBRecordset->Fields->GetCount();
            }
            
#pragma warning(push)
#pragma warning(disable: 4927) 
			std::string FieldName(short idx)
			{
				return mDBRecordset->Fields->GetItem(idx)->Name;
			}
#pragma warning( pop )

			a8::XValue GetValue(short idx)
			{
				a8::XValue xv;
				xv.SetVairent(mDBRecordset->Fields->GetItem(idx)->Value);
				return xv;
			}

			a8::XValue GetValue(const std::string &filedName)
			{
				a8::XValue xv;
                xv.SetVairent(mDBRecordset->Fields->GetItem(filedName.c_str())->Value);
				return xv;
			}
            
        private:
			a8::mssql::Connection *mConn;
            _CommandPtr	mDBCommand;
            _RecordsetPtr mDBRecordset;
            HRESULT mLastError;           
        };
    }
}

#endif
